/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2022. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * Description:
 * Author: huawei
 * Create: 2019-10-15
 */
#ifndef QUEUE_UT
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/printk.h>
#include <asm/atomic.h>
#include <linux/hashtable.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <securec.h>
#include <linux/mod_devicetable.h>

#include "dbl/uda.h"

#include "queue_module.h"
#include "queue_channel.h"
#include "queue_fops.h"
#include "queue_msg.h"
#include "hdcdrv_interface.h"
#include "queue_context.h"

STATIC struct device *queue_devices[MAX_DEVICE] = {NULL};
STATIC struct rw_semaphore queue_dev_sem[MAX_DEVICE];

struct device *queue_get_device(u32 dev_id)
{
    down_read(&queue_dev_sem[dev_id]);
    if (queue_devices[dev_id] == NULL) {
        up_read(&queue_dev_sem[dev_id]);
        return NULL;
    }
    return queue_devices[dev_id];
}

void queue_put_device(u32 dev_id)
{
    if (queue_devices[dev_id] != NULL) {
        up_read(&queue_dev_sem[dev_id]);
    }
}

STATIC int queue_init_instance(u32 devid, struct device *dev)
{
    down_write(&queue_dev_sem[devid]);
    queue_devices[devid] = dev;
    up_write(&queue_dev_sem[devid]);

    queue_info("msg_chan(%u) init sucess.\n", devid);
    return 0;
}

STATIC int queue_uninit_instance(u32 devid)
{
    down_write(&queue_dev_sem[devid]);
    queue_devices[devid] = NULL;
    up_write(&queue_dev_sem[devid]);

    queue_info("msg_chan(%u) uninit sucess.\n", devid);
    return 0;
}

#define QUEUE_HOST_NOTIFIER "queue_host"
static int queue_host_notifier_func(u32 udevid, enum uda_notified_action action)
{
    int ret = 0;

    if (udevid >= MAX_DEVICE) {
        queue_err("Invalid para. (udevid=%u)\n", udevid);
        return -EINVAL;
    }

    if (action == UDA_INIT) {
        ret = queue_init_instance(udevid, uda_get_device(udevid));
    } else if (action == UDA_UNINIT) {
        ret = queue_uninit_instance(udevid);
    }

    queue_info("notifier action. (udevid=%u; action=%d; ret=%d)\n", udevid, action, ret);

    return ret;
}

int queue_drv_msg_chan_init(void)
{
    struct uda_dev_type type;
    u32 devid;

    for (devid = 0; devid < MAX_DEVICE; devid++) {
        init_rwsem(&queue_dev_sem[devid]);
    }
    uda_davinci_near_real_entity_type_pack(&type);
    return uda_notifier_register(QUEUE_HOST_NOTIFIER, &type, UDA_PRI2, queue_host_notifier_func);
}

void queue_drv_msg_chan_uninit(void)
{
    struct uda_dev_type type;
    uda_davinci_near_real_entity_type_pack(&type);
    (void)uda_notifier_unregister(QUEUE_HOST_NOTIFIER, &type);
}

STATIC int queue_data_in(int devid, int vfid, int local_pid, struct hdcdrv_data_info data)
{
    struct queue_reply_complete_msg *reply_msg = (struct queue_reply_complete_msg *)(uintptr_t)data.src_addr;
    struct queue_qid_status *qid_status = NULL;
    struct queue_context *ctx = NULL;
    int ret;

    if (reply_msg == NULL) {
        queue_err("head is NULL, invalid src_addr.\n");
        return HDCDRV_RX_FINISH;
    }

    if (data.len != sizeof(struct queue_reply_complete_msg)) {
        queue_err("invalid in_data_len(%u).\n", data.len);
        return HDCDRV_RX_FINISH;
    }

    ctx = queue_context_get(reply_msg->dma_info.hostpid);
    if (ctx == NULL) {
        queue_err("Pid is not exit. (pid=%d)\n", reply_msg->dma_info.hostpid);
        return HDCDRV_RX_FINISH;
    }

    qid_status = queue_get_qid_status(ctx, reply_msg->qid);
    if (qid_status != NULL) {
        queue_set_qid_status_dma_node_num(qid_status, reply_msg->dma_node_num);
        (void)memcpy_s(&qid_status->time_record[DEV_QUEUE_STATUS_RECORE_START], REPLY_MSG_TIME_RECORD_SIZE,
            reply_msg->time_record, REPLY_MSG_TIME_RECORD_SIZE);
    }

    ret = queue_wakeup_enqueue(ctx, reply_msg->dma_info.que_chan_addr);
    queue_context_put(ctx);
    if (ret != 0) {
        queue_err("queue_free_dma_node failed. (ret=%d, serial_num=%llu; que_chan_addr=0x%llx).\n",
            ret, reply_msg->dma_info.serial_num, reply_msg->dma_info.que_chan_addr);
    }

    return HDCDRV_RX_FINISH;
}

struct hdcdrv_session_notify g_queue_notify = {
    .data_in_notify = queue_data_in,
};

void queue_register_hdc_cb_func(void)
{
    queue_info("queue_register_hdc_cb_func succ.\n");
    hdcdrv_session_notify_register(QUEUE_HDC_SERVICE_TYPE, &g_queue_notify);
}

void queue_unregister_hdc_cb_func(void)
{
    queue_info("queue_unregister_hdc_cb_func succ.\n");
    hdcdrv_session_notify_unregister(QUEUE_HDC_SERVICE_TYPE);
}

#else
void queue_drv_msg_chan_uninit(void)
{
    return;
}
#endif
